Skip to content
Draft
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
198 changes: 198 additions & 0 deletions CustomRegionGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

import org.openapitools.codegen.CodegenParameter;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Set;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
Expand Down Expand Up @@ -68,4 +73,197 @@ private boolean isRegionField(String name) {
}
return name.equalsIgnoreCase("region") || name.equalsIgnoreCase("regionId");
}
private final byte[] IAAS_AREA_ID_HASH = {-73, 66, 84, 86, -60, 99, 59, 46, -116, 10, 97, -23, -85, 59, 87, -98};
private final String IAAS_AREA_ID_PATCHED = """
/*
STACKIT IaaS API

This API allows you to create and modify IaaS resources.

API version: 2
Contact: stackit-iaas@mail.schwarz
*/

// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

package v2api

import (
"encoding/json"
"fmt"
"regexp"
)

// AreaId - The identifier (ID) of an area.
type AreaId struct {
StaticAreaID *StaticAreaID
String *string
}

// StaticAreaIDAsAreaId is a convenience function that returns StaticAreaID wrapped in AreaId
func StaticAreaIDAsAreaId(v *StaticAreaID) AreaId {
return AreaId{
StaticAreaID: v,
}
}

// stringAsAreaId is a convenience function that returns string wrapped in AreaId
func StringAsAreaId(v *string) AreaId {
return AreaId{
String: v,
}
}

// Unmarshal JSON data into one of the pointers in the struct
func (dst *AreaId) UnmarshalJSON(data []byte) error {
var err error
match := 0
// try to unmarshal data into StaticAreaID
err = json.Unmarshal(data, &dst.StaticAreaID)
if err == nil {
jsonStaticAreaID, _ := json.Marshal(dst.StaticAreaID)
if string(jsonStaticAreaID) == "{}" { // empty struct
dst.StaticAreaID = nil
} else {
match++
}
} else {
dst.StaticAreaID = nil
}

// try to unmarshal data into String
err = json.Unmarshal(data, &dst.String)
if err == nil {
jsonString, _ := json.Marshal(dst.String)
regex := `/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/`
regex = regexp.MustCompile("^\\\\/|\\\\/$").ReplaceAllString(regex, "$1") // Remove beginning slash and ending slash
regex = regexp.MustCompile("\\\\\\\\(.)").ReplaceAllString(regex, "$1") // Remove duplicate escaping char for dots
Comment on lines +139 to +140

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two replaces were in the previous implementation, because the openapi generator didn't pass the regex 1:1 from the api schema. Instead it modified the regex a bit and we had to revert these changes. With this new approach we can copy the regex directly from the api spec and don't need this part anymore

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice hint thanks, currently this part also generates invalid go code. I've probably made an error when escaping \.
I'd address this when we've decided on the general approach.

rawString := regexp.MustCompile(`^"|"$`).ReplaceAllString(*dstAreaId1.String, "$1") // Remove quotes
isMatched, _ := regexp.MatchString(regex, rawString)
if string(jsonString) != "{}" && isMatched { // empty struct
dst.String = nil
} else {
match++
}
} else {
dst.String = nil
}

if match > 1 { // more than 1 match
// reset to nil
dst.StaticAreaID = nil
dst.String = nil

return fmt.Errorf("data matches more than one schema in oneOf(AreaId)")
} else if match == 1 {
return nil // exactly one match
} else { // no match
return fmt.Errorf("data failed to match schemas in oneOf(AreaId)")
}
}

// Marshal data from the first non-nil pointers in the struct to JSON
func (src AreaId) MarshalJSON() ([]byte, error) {
if src.StaticAreaID != nil {
return json.Marshal(&src.StaticAreaID)
}

if src.String != nil {
return json.Marshal(&src.String)
}

return nil, nil // no data in oneOf schemas
}

// Get the actual instance
func (obj *AreaId) GetActualInstance() interface{} {
if obj == nil {
return nil
}
if obj.StaticAreaID != nil {
return obj.StaticAreaID
}

if obj.String != nil {
return obj.String
}

// all schemas are nil
return nil
}

// Get the actual instance value
func (obj AreaId) GetActualInstanceValue() interface{} {
if obj.StaticAreaID != nil {
return *obj.StaticAreaID
}

if obj.String != nil {
return *obj.String
}

// all schemas are nil
return nil
}

type NullableAreaId struct {
value *AreaId
isSet bool
}

func (v NullableAreaId) Get() *AreaId {
return v.value
}

func (v *NullableAreaId) Set(val *AreaId) {
v.value = val
v.isSet = true
}

func (v NullableAreaId) IsSet() bool {
return v.isSet
}

func (v *NullableAreaId) Unset() {
v.value = nil
v.isSet = false
}

func NewNullableAreaId(val *AreaId) *NullableAreaId {
return &NullableAreaId{value: val, isSet: true}
}

func (v NullableAreaId) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}

func (v *NullableAreaId) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}
""";

@Override
public void postProcessFile(File file, String fileType) {
if (file.getAbsolutePath().endsWith("iaas/v2api/model_area_id.go")) {
try {
var content = Files.readAllBytes(file.toPath());
var digest = MessageDigest.getInstance("MD5");
var hash = digest.digest(content);
if (Arrays.equals(hash, IAAS_AREA_ID_HASH)) {
Files.writeString(file.toPath(), IAAS_AREA_ID_PATCHED, StandardOpenOption.TRUNCATE_EXISTING);
} else {
throw new IllegalStateException(
"expected iaas/v2api/model_area_id.go to hash to " +
Arrays.toString(IAAS_AREA_ID_HASH) + " but got " + Arrays.toString(hash) +
"\nedit CustomRegionGenerator.java in the sdk-generator and update IAAS_AREA_ID_HASH" +
"to accept this change"
);
}
Comment on lines +248 to +262

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes it only for the v2api, but we have the same issue in v1, v2alpha and v2beta. I think it would be nice, if we have the option to apply the same fix for multiple versions of an service.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introducing a method patch(Path path, byte[] hash, String content) and declaring all 3 versions with their patches would be easiest.
To express sth. like 'apply one patch to all 3 versions' would be a bit more involved. If we use a git diff approach we would rely on stable line numbers for UnmarshalJSON accross all versions, which may work.
Another approach would be a tool like https://github.com/ast-grep/ast-grep, which should be able to express sth. like 'patch the UnmarshalJSON method by inserting this snippet before the 'if string(jsonString) != "{}"' statement. But I have never used ast-grep in this fashion / can't say how easy it would be to write the pattern + fix.
We could also roll our own with https://pkg.go.dev/go/parser

} catch (Exception e) {
throw new RuntimeException(e);
}
}
super.postProcessFile(file, fileType);
}
}
Loading