openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG][Dart] Incorrectly handling of `type: string` combined with `format: decimal`

Open clintonb opened this issue 3 years ago • 3 comments

Bug Report Checklist

  • [x] Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] Have you tested with the latest master to confirm the issue still exists?
  • [x] Have you searched for related issues/PRs?
  • [x] What's the actual output vs expected output?
  • [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

The Dart generator maps properties with type: string and format: decimal as doubles when parsing JSON. The API returns a string, as described in the schema. Given the type mismatch, the mapValueOfType helper method sets the field to null.

If the field is required, a runtime error occurs because the fromJson method uses the bang operator (!) to assert the field is not null but mapValueOfType returns null.

openapi-generator version

The incorrect mapping of decimal to double has existed since at least 5.4.0. The runtime issue appeared in 6.0.0 with the introduction of the bang operator (!).

I am currently using 6.2.1.

OpenAPI declaration file content or url

https://raw.githubusercontent.com/alpacahq/bkdocs/564d49f52bc91bb180b15b2b900253a1e7c53846/assets/openapi.yaml

Generation Details
java -jar openapi-generator-cli.jar generate -i https://raw.githubusercontent.com/alpacahq/bkdocs/564d49f52bc91bb180b15b2b900253a1e7c53846/assets/openapi.yaml -g dart --global-property apiDocs=false,modelDocs=false -o tmp
Steps to reproduce
  1. Generate the client library.
  2. Note the use of lastEquity: mapValueOfType<double>(json, r'last_equity') in tmp/lib/model/account_extended.dart.
  3. Call AccountExtended.fromJson() with last_equity set to a string value.
  4. Observe that the return value for lastEquity is null.
Related issues/PRs

https://github.com/OpenAPITools/openapi-generator/issues/10836

Suggest a fix

Consider using an actual Decimal type when parsing decimal values: https://pub.dev/packages/decimal.

clintonb avatar Jan 18 '23 19:01 clintonb

@jaumard (2018/09) @josh-burton (2019/12) @amondnet (2019/12) @sbu-WBT (2020/12) @kuhnroyal (2020/12) @agilob (2020/12) @ahmednfwela (2021/08)

ahmednfwela avatar May 28 '23 18:05 ahmednfwela

reproduced as well.

If a field has type: string and format: decimal, then the resulting struct field gets type float64 assigned.

jaylevin avatar Jun 02 '23 23:06 jaylevin

This was blocking me on a project but I managed to get it mostly working. There's a lingering bug in how it handles required decimal fields -- the generated code uses Decimal.from_json(...)! but the ! is unnecessary (it's easy enough to remove manually). I'm not sure I'll have time to finish out a formal PR but wanted to share the patch at least for others who come across this so they can get something workable.

index 5bf7fd5df30..06e23c383c0 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java
@@ -148,7 +148,7 @@ public abstract class AbstractDartCodegen extends DefaultCodegen {
         typeMapping.put("number", "num");
         typeMapping.put("float", "double");
         typeMapping.put("double", "double");
-        typeMapping.put("decimal", "double");
+        typeMapping.put("decimal", "Decimal");
         typeMapping.put("integer", "int");
         typeMapping.put("Date", "DateTime");
         typeMapping.put("date", "DateTime");
@@ -168,6 +168,7 @@ public abstract class AbstractDartCodegen extends DefaultCodegen {
                 "int",
                 "num",
                 "double",
+		"Decimal",
                 "List",
                 "Set",
                 "Map",
@@ -183,6 +184,7 @@ public abstract class AbstractDartCodegen extends DefaultCodegen {
         imports.put("List", "dart:core");
         imports.put("Set", "dart:core");
         imports.put("Map", "dart:core");
+	imports.put("Decimal", "package:decimal/decimal.dart");
         imports.put("DateTime", "dart:core");
         imports.put("Object", "dart:core");
         imports.put("MultipartFile", "package:http/http.dart");
diff --git a/modules/openapi-generator/src/main/resources/dart2/apilib.mustache b/modules/openapi-generator/src/main/resources/dart2/apilib.mustache
index 1b1898d88dd..c796ea1a82a 100644
--- a/modules/openapi-generator/src/main/resources/dart2/apilib.mustache
+++ b/modules/openapi-generator/src/main/resources/dart2/apilib.mustache
@@ -6,6 +6,7 @@ import 'dart:convert';
 import 'dart:io';
 
 import 'package:collection/collection.dart';
+import 'package:decimal/decimal.dart';
 import 'package:http/http.dart';
 import 'package:intl/intl.dart';
 import 'package:meta/meta.dart';
diff --git a/modules/openapi-generator/src/main/resources/dart2/pubspec.mustache b/modules/openapi-generator/src/main/resources/dart2/pubspec.mustache
index af522867b03..b77b10615b0 100644
--- a/modules/openapi-generator/src/main/resources/dart2/pubspec.mustache
+++ b/modules/openapi-generator/src/main/resources/dart2/pubspec.mustache
@@ -19,6 +19,7 @@ dependencies:
   http: '>=0.13.0 <0.14.0'
   intl: any
   meta: '^1.1.8'
+  decimal: '^2.3.3'
 dev_dependencies:
   test: '>=1.21.6 <1.22.0'
 {{#json_serializable}}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java
index 03848b4f802..b31c0ac2596 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java
@@ -100,8 +100,8 @@ public class DartModelTest {
 
         final CodegenProperty property6 = cm.vars.get(5);
         Assert.assertEquals(property6.baseName, "decimal");
-        Assert.assertEquals(property6.dataType, "double");
-        Assert.assertEquals(property6.baseType, "double");
+        Assert.assertEquals(property6.dataType, "Decimal");
+        Assert.assertEquals(property6.baseType, "Decimal");
     }
 
     @Test(description = "convert a model with list property")

matthewgrimes avatar Apr 25 '24 03:04 matthewgrimes