Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions mock-server/app/http.php
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,15 @@
]);
});

App::get('/v1/mock/tests/general/presets/empty')
->desc('List Empty Presets')
->groups(['mock'])
->label('scope', 'public')
->inject('response')
->action(function (UtopiaSwooleResponse $response) {
$response->json(['total' => 0]);
});

App::get('/v1/mock/tests/compound')
->desc('Get compound union')
->groups(['mock'])
Expand Down
14 changes: 14 additions & 0 deletions src/SDK/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,20 @@ protected function getSchema(Schema|Parameter $value): Schema
return $value instanceof Parameter ? ($value->schema ?? new AnySchema()) : $value;
}

/**
* The value a model fills a required field with when a response omits it. A collection the
* spec gives no default is filled empty, the only value its type always admits; a nested model
* stays absent, since an empty object may not satisfy the fields it requires.
*/
public function getModelDefault(Schema $property, bool $required): mixed
{
if (!$required) {
return null;
}

return $property->default ?? ($property instanceof ArraySchema ? [] : null);
}

protected function getSchemaType(Schema|Parameter $value): string
{
$schema = $this->getSchema($value);
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Language/Dart.php
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ protected function getDartLiteral(mixed $value): string

protected function getDartDefault(Schema $property): string
{
$value = $this->getSchema($property)->default;
$value = $this->getModelDefault($this->getSchema($property), true);

if (is_array($value) && $this->getSchemaType($property) === self::TYPE_OBJECT) {
return $this->getDartMap($value);
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Language/DotNet.php
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ protected function getToMapExpression(Schema $property, string $resolvedName, Sp

protected function formatCSharpDefault(Schema $property): string
{
$value = $this->getSchema($property)->default;
$value = $this->getModelDefault($this->getSchema($property), true);

if (is_array($value) && $this->getSchemaType($property) === self::TYPE_OBJECT) {
return $this->formatCSharpDictionary($value);
Expand Down
5 changes: 3 additions & 2 deletions src/SDK/Language/JS.php
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,9 @@ protected function getResponseDefaults(Operation $method, Specification $spec, i
}
$models[$name] = ['defaults' => [], 'properties' => []];
foreach ($model->properties as $key => $property) {
if ($property->default !== null && \in_array($key, $model->required, true)) {
$models[$name]['defaults'][$key] = $this->getLiteral($property->default);
$default = $this->getModelDefault($property, \in_array($key, $model->required, true));
if ($default !== null) {
$models[$name]['defaults'][$key] = $this->getLiteral($default);
}
$shape = $this->getDefaultsShape($property, $spec);
if ($shape !== null) {
Expand Down
4 changes: 2 additions & 2 deletions src/SDK/Language/Kotlin.php
Original file line number Diff line number Diff line change
Expand Up @@ -624,8 +624,8 @@ protected function getPropertyAssignment(Schema $property, Specification $spec):
}

$mapKey = 'map["' . \str_replace('$', '\\$', $propertyName) . '"]';
$default = $this->getSchema($property)->default;
if ($required && $default !== null) {
$default = $this->getModelDefault($property, $required);
if ($default !== null) {
$mapKey = '(if (map.containsKey(' . $this->getStringLiteral($propertyName, 'kotlin') . ')) ' . $mapKey
. ' else ' . $this->getValueLiteral($default, $property, $spec) . ')';
}
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Language/Ruby.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ protected function jsonToHash(array $data, int $indent = 0): string

protected function formatRubyDefault(Schema|Parameter $property): string
{
$default = $this->getSchema($property)->default;
$default = $this->getModelDefault($this->getSchema($property), true);

if (is_array($default) && $this->getSchemaType($property) === self::TYPE_OBJECT) {
return $this->formatRubyHash($default);
Expand Down
6 changes: 3 additions & 3 deletions src/SDK/Language/Rust.php
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ function ($value, $indent = 0): string {
}, ["is_safe" => ["html"]]),
new TwigFilter("inputType", fn(Schema|Parameter $property, ?Specification $spec = null, string $generic = "serde_json::Value"): string => $this->getInputType($property, $spec)),
new TwigFilter("paramValue", fn(Schema|Parameter $property, string $paramName, ?Specification $spec = null): string => $this->getParamValue($property, $paramName, $spec), ["is_safe" => ["html"]]),
new TwigFilter("rustDefault", fn(Schema $property, ?Specification $spec = null): string => $this->getModelDefault($property, $spec), ["is_safe" => ["html"]]),
new TwigFilter("rustDefault", fn(Schema $property, ?Specification $spec = null): string => $this->getRustDefault($property, $spec), ["is_safe" => ["html"]]),
new TwigFilter("rustType", fn($value): string|array => str_replace(['&lt;', '&gt;'], ['<', '>'], $value), ["is_safe" => ["html"]]),
new TwigFilter("rustCrateName", fn($value): string|array => str_replace('-', '_', $value)),
new TwigFilter("stripProtocol", fn($value): string|array => str_replace(['https://', 'http://'], '', $value)),
Expand Down Expand Up @@ -864,9 +864,9 @@ protected function rustfmtHeaderInsert(int $indent, string $key, string $valueEx
return $this->rustfmtCall($indent, 'next.config.headers.insert', $args);
}

protected function getModelDefault(Schema $property, ?Specification $spec = null): string
protected function getRustDefault(Schema $property, ?Specification $spec = null): string
{
$value = $this->getSchema($property)->default;
$value = $this->getModelDefault($this->getSchema($property), true);
$json = is_array($value) && $this->getSchemaType($property) === self::TYPE_OBJECT
? $this->getRustJsonMap($value)
: $this->getRustJson($value);
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Language/Swift.php
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ public function getFilters(): array
}),
new TwigFilter('modelToMapValue', fn(Schema $property, string $name, bool $required): string => $this->getModelToMapValue($property, $name, $required), ['is_safe' => ['html']]),
new TwigFilter('swiftLiteral', fn(mixed $value): string => $this->swiftLiteral($value, 0), ['is_safe' => ['html']]),
new TwigFilter('swiftErasedDefault', fn(Schema $property): string => $this->erasedLiteral($property->default, $property instanceof ArraySchema ? false : ($property->default === [] ? true : null), $property instanceof NumberSchema), ['is_safe' => ['html']]),
new TwigFilter('swiftErasedDefault', fn(Schema $property): string => $this->erasedLiteral($this->getModelDefault($property, true), $property instanceof ArraySchema ? false : ($property->default === [] ? true : null), $property instanceof NumberSchema), ['is_safe' => ['html']]),
new TwigFilter('swiftDoc', fn(string $value, int $width = 75, string $prefix = ' ///'): string => $this->swiftDoc($value, $width, $prefix), ['is_safe' => ['html']]),
];
}
Expand Down
1 change: 1 addition & 0 deletions src/SDK/SDK.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public function __construct(protected Language $language, protected Specificatio
$this->twig->addFilter(new TwigFilter('schemaRequired', fn(Schema|Parameter $value): bool => $value instanceof Parameter ? $value->required : isset($this->requiredSchemas[\spl_object_id($value)])));
$this->twig->addFilter(new TwigFilter('schemaExample', fn(Schema|Parameter $value): mixed => $this->getSchema($value)->example));
$this->twig->addFilter(new TwigFilter('schemaDefault', fn(Schema|Parameter $value): mixed => $this->getSchema($value)->default));
$this->twig->addFilter(new TwigFilter('modelDefault', fn(Schema $value): mixed => $this->language->getModelDefault($value, isset($this->requiredSchemas[\spl_object_id($value)]))));
$this->twig->addFilter(new TwigFilter('enumName', fn(Schema|Parameter $value): string => $this->getEnumName($value)));
$this->twig->addFilter(new TwigFilter('enumKeys', fn(Schema|Parameter $value): array => $this->language->resolveEnumKeys($value)));
$this->twig->addFilter(new TwigFilter('operations', fn(Tag $tag): array => $this->getFilteredMethods($this->getMethods($tag->name), $tag->name)));
Expand Down
2 changes: 1 addition & 1 deletion templates/dart/lib/src/models/model.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class {{ definitionName | caseUcfirst | overrideIdentifier }} implements Model {
factory {{ definitionName | caseUcfirst | overrideIdentifier }}.fromMap(
Map<String, dynamic> map,
) {
{% set defaultedProperties = definition.properties | filter((property) => (property | schemaRequired) and (property | schemaDefault) is not null) %}
{% set defaultedProperties = definition.properties | filter((property) => (property | modelDefault) is not null) %}
{% if defaultedProperties is not empty %}
map = {
{% for property in defaultedProperties %}
Expand Down
2 changes: 1 addition & 1 deletion templates/deno/src/services/service.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { basename } from "https://deno.land/std@0.122.0/path/mod.ts";
import { Service } from '../service.ts';
{% set usesDefaults = false %}
{% for method in (service | operations) %}
{% if (method | responseDefaults(spec, 8)) is not empty %}{% set usesDefaults = true %}{% endif %}
{% if 'multipart/form-data' not in (method | consumes) and (method | responseDefaults(spec, 8)) is not empty %}{% set usesDefaults = true %}{% endif %}
{% endfor %}
import { Payload, Client{% if usesDefaults %}, applyDefaults{% endif %} } from '../client.ts';
import { InputFile } from '../inputFile.ts';
Expand Down
2 changes: 1 addition & 1 deletion templates/dotnet/Package/Models/Model.cs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace {{ spec.info.title | caseUcfirst }}.Models

public static {{ definitionName | caseUcfirst | overrideIdentifier }} From(Dictionary<string, object> map) => new {{ definitionName | caseUcfirst | overrideIdentifier }}(
{%~ for property in definition.properties %}
{%- set source = (property | schemaDefault) is not null
{%- set source = (property | modelDefault) is not null
? '(map.ContainsKey("' ~ (property | schemaName) ~ '") ? map["' ~ (property | schemaName) ~ '"] : ' ~ (property | csharpDefault) ~ ')'
: 'map["' ~ (property | schemaName) ~ '"]' %}
{{ (property | schemaName) | caseCamel | escapeKeyword | removeDollarSign }}:{{' '}}
Expand Down
2 changes: 1 addition & 1 deletion templates/node/src/services/template.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% set usesException = false %}
{% set usesDefaults = false %}
{% for method in (service | operations) %}
{% if (method | responseDefaults(spec, 8)) is not empty %}{% set usesDefaults = true %}{% endif %}
{% if 'multipart/form-data' not in (method | consumes) and (method | responseDefaults(spec, 8)) is not empty %}{% set usesDefaults = true %}{% endif %}
{% if 'multipart/form-data' in (method | consumes) %}{% set usesUpload = true %}{% endif %}
{% if 'Models.' in (method | getReturn(spec)) or 'Models.' in ((method | responseModel) | getGenerics(spec)) %}{% set usesModels = true %}{% endif %}
{% for parameter in (method | parameters('all')) %}
Expand Down
2 changes: 1 addition & 1 deletion templates/node/test/services/service.test.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('{{ service.name | caseUcfirst }}', () => {
{%- if responseModelName %}
{%- set dataBody %}
{%- for definitionName, definition in definitions ~%}{%~ if definitionName == responseModelName -%}{%~ for property in definition.properties | filter((param) => param | schemaRequired) ~%}
{% set propValue %}{% if (property | schemaType) == 'object' %}{}{% elseif (property | schemaType) == 'array' %}[]{% elseif (property | schemaType) == 'string' %}{{ ((property | schemaExample) | escapeDollarSign) | jsString | raw }}{% elseif (property | schemaType) == 'boolean' %}true{% else %}{{ (property | schemaExample) }}{% endif %}{% endset %}{{ (((property | schemaName) | escapeDollarSign) | jsKey) | jsExampleEntry(propValue, '', 12) | raw }}
{% set propValue %}{% if (property | schemaType) == 'object' %}{}{% elseif (property | schemaType) == 'array' %}[]{% elseif (property | schemaType) == 'string' %}{{ ((property | schemaExample) | escapeDollarSign) | jsString | raw }}{% elseif (property | schemaType) == 'boolean' %}true{% else %}{{ (property | schemaExample) }}{% endif %}{% endset %}{{ ((property | schemaName) | jsKey) | jsExampleEntry(propValue, '', 12) | raw }}
{%- endfor %}{% set break = true %}{%- else -%}{% set continue = true %}{%- endif -%}{%~ endfor %}{% endset %}
const data = {% if dataBody | trim is empty %}{}{% else %}{{ '{' }}{{ dataBody | raw }}
}{% endif %};
Expand Down
6 changes: 3 additions & 3 deletions templates/php/src/Models/Model.php.twig
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ namespace {{ namespace | caseNamespace }}\Models;

{% set requiredProperties = definition.properties | filter((property) => (property | schemaRequired)) %}
{% set optionalProperties = definition.properties | filter((property) => not (property | schemaRequired)) %}
{% set defaultedProperties = requiredProperties | filter((property) => (property | schemaDefault) is not null) %}
{% set checkedProperties = requiredProperties | filter((property) => (property | schemaDefault) is null) %}
{% set defaultedProperties = requiredProperties | filter((property) => (property | modelDefault) is not null) %}
{% set checkedProperties = requiredProperties | filter((property) => (property | modelDefault) is null) %}
{% set enumImports = [] %}
{% for property in definition.properties %}
{% if (property | enumValues) is not empty %}
Expand Down Expand Up @@ -83,7 +83,7 @@ readonly class {{ definitionName | caseUcfirst | overrideIdentifier }}
{% if defaultedProperties is not empty %}
$data += [
{% for property in defaultedProperties %}
'{{ (property | schemaName) }}' => {{ property | schemaDefault | phpLiteral }},
'{{ (property | schemaName) }}' => {{ property | modelDefault | phpLiteral }},
{% endfor %}
];
{% if checkedProperties is not empty %}
Expand Down
4 changes: 2 additions & 2 deletions templates/python/package/models/model.py.twig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% set hasGenericProperty = definition.properties|hasGenericTypeProperty(spec) %}
{% set isGeneric = definition.additionalProperties or hasGenericProperty %}
{% set defaulted = definition.properties | filter(property => (property | schemaRequired) and (property | schemaDefault) is not null) %}
{% set defaulted = definition.properties | filter(property => (property | modelDefault) is not null) %}
from typing import Any, Dict, List, Optional, Union, cast{% if isGeneric %}, Generic, TypeVar, Type{% endif %}

from pydantic import Field, PrivateAttr{{ definition.additionalProperties ? ', TypeAdapter' : '' }}{{ defaulted is not empty ? ', model_validator' : '' }}{{ definition.additionalProperties ? ', model_serializer' : '' }}
Expand Down Expand Up @@ -82,7 +82,7 @@ class {{ definitionName | caseUcfirst }}(AppwriteModel{% if isGeneric %}, Generi
{% for property in defaulted %}
{% set fieldName = property | getModelFieldName(definition.properties) %}
if '{{ property | schemaName }}' not in data{% if fieldName != (property | schemaName) %} and '{{ fieldName }}' not in data{% endif %}:
data['{{ property | schemaName }}'] = {{ property | schemaDefault | pythonLiteral | raw }}
data['{{ property | schemaName }}'] = {{ property | modelDefault | pythonLiteral | raw }}
{% endfor %}
return data
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion templates/react-native/src/services/template.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{% set usesException = false %}
{% set usesDefaults = false %}
{% for method in (service | operations) %}
{% if (method | responseDefaults(spec, 8)) is not empty %}{% set usesDefaults = true %}{% endif %}
{% if 'multipart/form-data' not in (method | consumes) and (method | responseDefaults(spec, 8)) is not empty %}{% set usesDefaults = true %}{% endif %}
{% if 'multipart/form-data' in (method | consumes) %}{% set usesUpload = true %}{% endif %}
{% if 'multipart/form-data' in (method | consumes) and not (method | textResponse) %}{% set usesChunkedUpload = true %}{% endif %}
{% if 'Models.' in (method | getReturn(spec)) or 'Models.' in ((method | responseModel) | getGenerics(spec)) %}{% set usesModels = true %}{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion templates/ruby/lib/container/models/model.rb.twig
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module {{ spec.info.title | caseUcfirst }}
end

def self.from(map:)
{% set defaults = definition.properties | filter(p => (p | schemaRequired) and (p | schemaDefault) is not null) %}
{% set defaults = definition.properties | filter(p => (p | modelDefault) is not null) %}
{% if defaults | length > 0 %}
map = { {% for property in defaults %}"{{ property | schemaName }}" => {{ property | rubyDefault }}{% if not loop.last %}, {% endif %}{% endfor %} }.merge(map)
{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions templates/rust/src/models/model.rs.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
{% endif %}
{% set additionalFieldName = definition.properties.data is defined ? 'additional_data' : 'data' %}
{% set defaultedProperties = definition.properties | filter((property) => (property | schemaRequired) and (property | schemaDefault) is not null) %}
{% set defaultedProperties = definition.properties | filter((property) => (property | modelDefault) is not null) %}

{{ definition.description | rustdocComment }}
#[derive(Debug, Clone, Serialize, Deserialize)]
Expand All @@ -17,7 +17,7 @@ pub struct {{ definitionName | caseUcfirst | overrideIdentifier }} {
{% set fieldName = fieldName | overrideIdentifier %}
{{ property.description | rustdocComment(4) }}
#[serde(rename = "{{ (property | schemaName) }}")]
{% if (property | schemaRequired) and (property | schemaDefault) is not null %}
{% if (property | modelDefault) is not null %}
#[serde(default = "default_{{ fieldName | replace({'r#': ''}) }}")]
{% endif %}
{% if (property | schemaRequired) and not (property | schemaNullable) %}
Expand Down
1 change: 1 addition & 0 deletions templates/rust/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ async fn test_general_service(client: &Client, string_in_array: &[String]) -> Re
for item in general.list_presets().await?.presets {
println!("preset: name={}, count={}, enabled={}", item.name, item.count, item.enabled);
}
println!("empty presets: {}", general.list_empty_presets().await?.presets.len());

// Exception Responses
match general.error400().await {
Expand Down
Loading
Loading