Skip to content

MAINT: Refactor dsc-lib for consistency, library API usage, and maintainability #1391

@michaeltlombardi

Description

@michaeltlombardi

Summary of the new feature / enhancement

As a maintainer of the dsc-lib crate and integrating developer of the same crate,
I want to be able to more quickly navigate, contribute, test, document, and reference the types and functions in the library
So that I can contribute and integrate more quickly and reliably.

The layout and library API for dsc-lib has grown organically from the beginning of the project. Along the way, we've learned a lot about writing idiomatic and performant Rust code that differs from the current structure.

Additionally, when we first began work on dsc-lib, we worked under the assumption that the only Rust projects that would take a dependency on the library are the dsc CLI and a limited set of crates developed in this monorepo. Now that the Bicep local extension depends on dsc-lib and we plan to develop a Development Kit in Rust for DSC resources and extensions, we should carefully consider updating the library to follow more consistent conventions for the library APIs and structure.

Proposed technical implementation details (optional)

There are a few different areas we can pursue as part of this overall effort:

  1. Decomposing large rust files into submodules with the re-export pattern.

    We're currently using this pattern to various degrees with the types module. We're almost using this pattern for the functions, except that the submodules are public, so you reference a function type as dsc_lib::functions::<snake_case_name>::<PascalCaseName>. Making this change would enable a simpler access for these types, like dsc_lib::functions::CreateArray instead of dsc_lib::functions::create_array::CreateArray.

    This would also enable decomposing larger files with numerous types into smaller files that are easier to read, maintain, and reason about.

    [!NOTE]
    While out of scope for this issue, it will also make it easier for us to extract subsets of functionality into separate crates to improve compile time performance (for Rust, the crate is a compilation unit). For example, if we decomposed the dscresources module into a dsc-lib-resources crate and re-exported it from within dsc-lib, any compilation for local development that doesn't affect the decomposed crate is able to reuse prior compilation. I'm not sure the performance benefits are worth this more drastic move, but we might find that to be the case in the future.

  2. Reviewing the naming for structs, functions, and other items for consistency and clarity.

    Currently, we have a mix of naming conventions that have organically grown with the project. We should be more consistent in the naming for types, traits, and functions both to ease general cognitive load for maintainers and to provide a consistent model for integrating developers to rely on.

  3. Standardize reference documentation for items in the library.

    Currently, the reference documentation (triple slash docs above a definition) for the library is in a mixed state. Some definitions have extensive documentation, some have a single line, some are lacking documentation entirely. We should ensure we have coverage of every public item and that the structure we use is consistent. This is critical for integrating developers but also helpful for contributors and maintainers, since reference documentation is surfaced by the Rust Analyzer extension when working on the project.

    We should also consider documenting private items to the same standard, though this only affects maintainers.

  4. Moving shared type definitions into the types module.

    As we move forward with the schema canonicalization effort and adopting the "parse, don't validate" design pattern to improve reliability and coherence for the library, we should consider moving shared type definitions to that module. Not every type should be defined there - manifests belong in their relevant module, for example.

    But some types are defined in a given module and reused across multiple modules as generic types, like ExecutionType.

  5. Moving all Rust tests for public items into the tests/integration suite.

    The primary motivating factor here is build and test timings. When integration tests are colocated with module code, any changes to either the tests or the implementation requires recompilation because the file changed.

    Moving the tests and following the current pattern that mirrors the src directory structure can also help us quickly identify where we are lacking integration tests for the library. Currently, given the existing layout, we need to carefully check every implementation file for test coverage.

  6. Moving all Rust tests for private items into the src/tests folder.

    We don't seem to have many tests that validate the behavior of private items. The vast majority of the tests I reviewed are only validating public items. However, moving the tests to a dedicated unit tests folder will also improve compile times for local development similarly to moving integration tests.

    In the process of moving tests out of the implementation files and into dedicated test files, we should consider whether the unit tests are providing any value over the integration tests.

  7. Enhancing integration tests for faster feedback and improved reliability.

    Currently, we use Pester tests in the dsc folder as acceptance tests for the DSC CLI. We should continue to do so, as the CLI is its own fully separate boundary for users. However, given the need to make the dsc-lib crate available itself, we should provide integration tests that ensure correctness and reliability for the underlying APIs themselves.

    Rust integration tests are also much faster than repeatedly invoking dsc through Pester, so this can improve the local development feedback loop for contributors and maintainers. We can continue to rely on the acceptance tests in the dsc folder for CI.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions