-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Title: Refactor Ruby SDK Models with Shale Mapper via ZitadelModel Base Class
Description:
This initiative will refactor the Ruby SDK's model classes to leverage the Shale gem for object mapping and JSON serialization/deserialization. The goal is to significantly reduce boilerplate, improve maintainability, and adopt a more standard Ruby approach by introducing a common ZitadelModel base class and updating model generation templates.
Problem:
The current Ruby models, generated by OpenAPI Generator, include substantial duplicated code and rely on conventions like attribute_map, openapi_types, build_from_hash, to_hash, and helper methods in ApiClient for serialization/deserialization. This leads to:
- Increased maintenance overhead for models.
- Less idiomatic Ruby compared to modern mapping libraries.
- Potential inconsistencies in serialization logic.
Impact:
Refactoring will result in:
- Slimmer, cleaner, and more maintainable model classes using Shale's DSL.
- Standardized and robust JSON handling powered by the Shale gem.
- Reduced risk of bugs associated with custom serialization logic.
- Improved developer experience when working with SDK models.
Solution / Tasks:
1. Implement ZitadelModel Base Class:
Create a module ZitadelModel or class ZitadelModel that includes Shale::Mapper. This base will provide common JSON and Hash serialization/deserialization methods for all SDK models to inherit or include. Since Shale's mapping is defined in the inheriting class, the base might primarily provide convenience methods wrapping Shale's core functionality.
Example ZitadelModel.rb (using a base class approach):
require 'shale'
module Zitadel
module Client # Or your chosen SDK namespace
class ZitadelModel < Shale::Mapper
# Shale::Mapper provides from_json, to_json, from_hash, to_hash
# We can add aliases or wrappers if needed, but often direct usage is fine.
# Example wrapper for consistency if desired:
def to_zitadel_hash(mode: :openapi)
# Shale's to_hash mode :openapi might be relevant if defined
# Default to_hash produces string keys suitable for JSON
to_hash
end
def to_zitadel_json(**options)
to_json(**options) # Pass options like :pretty
end
def self.from_zitadel_hash(hash_data)
from_hash(hash_data)
end
def self.from_zitadel_json(json_string)
from_json(json_string)
end
end
end
end2. Update OpenAPI Generator Templates for Ruby Models:
Modify the OpenAPI Generator templates for Ruby models to:
- Make generated models inherit from
ZitadelModel(or include the module). - Define attributes using Shale's
attributeDSL (e.g.,attribute :name, Shale::Type::String,attribute :details, UserServiceDetailsMapper). - Use Shale's
json { map 'jsonKey', to: :ruby_attribute }block for mapping differing JSON keys to Ruby attribute names. - Remove the old boilerplate code (static
attribute_map,openapi_types,build_from_hash,_deserialize,to_hash,_to_hash, etc.). - Ensure enums are handled as simple string/integer properties or custom Shale types if needed.
- Handle default values using Shale's
default: -> { ... }option on attributes.
Target structure for a generated model (e.g., user_service_user.rb):
require 'shale'
# Assuming other models (UserServiceDetailsMapper etc.) are also defined using Shale
# and inherit from ZitadelModel
module Zitadel
module Client
# Assuming UserServiceUserState is handled, e.g., as Shale::Type::String
class UserServiceUser < ZitadelModel
attribute :user_id, Shale::Type::String
attribute :details, UserServiceDetailsMapper, default: -> { nil }
attribute :state, Shale::Type::String # Or custom type/enum handling
attribute :username, Shale::Type::String
attribute :login_names, Shale::Type::Array.of(Shale::Type::String), default: -> { [] }
attribute :preferred_login_name, Shale::Type::String, default: -> { nil }
attribute :human, UserServiceHumanUserMapper, default: -> { nil }
attribute :machine, UserServiceMachineUserMapper, default: -> { nil }
json do
map 'userId', to: :user_id
map 'details', to: :details
map 'state', to: :state
map 'username', to: :username
map 'loginNames', to: :login_names
map 'preferredLoginName', to: :preferred_login_name
map 'human', to: :human
map 'machine', to: :machine
end
# The initialize method is provided by Shale::Mapper
# to_hash, from_hash, to_json, from_json are inherited/provided by Shale::Mapper
end
end
end3. Update SDK Code to Use New Model Methods:
- Search the SDK codebase (likely within
ApiClientor similar) for usages of the old deserialization logic (ApiClient#deserialize,ApiClient#convert_to_type,Model.build_from_hash). - Replace these calls with
ModelClass.from_json(response_body_string)orModelClass.from_hash(parsed_hash). - Search for usages of the old serialization logic (
ApiClient#object_to_http_body,instance.to_hash). - Replace these calls with
$instance.to_json()(if a JSON string is needed for the request body) or$instance.to_hash()(if a hash is needed). - The aim is to eliminate the models' dependency on the old custom serialization/deserialization helpers.
Expected Outcomes:
- Ruby SDK models are significantly leaner, using Shale's DSL for definition and inheriting ser/des logic.
- JSON serialization and deserialization are handled robustly by the Shale gem.
- The custom serialization/deserialization logic within
ApiClientand the old model boilerplate is removed. - The SDK is easier to maintain and aligns better with modern Ruby practices.
- Functional equivalence for JSON-based API interactions is preserved.
Additional Notes:
- Dependencies: Ensure
Gemfileincludesgem 'shale'. - Null Handling: Review Shale's handling of
nilvalues during serialization (it generally includes explicitnulls if the attribute isnilin Ruby). Configure as needed if omission is preferred. - Testing: Thorough testing of serialization/deserialization for various model types (including nested objects, arrays, dates, etc.) is essential.
- Discriminators: Functionality related to discriminators is explicitly out of scope for this task.