Skip to content

2.0.0

Compare
Choose a tag to compare
@commonsensesoftware commonsensesoftware released this 05 Feb 22:05
· 93 commits to main since this release

The 2.0 release is largely the same as the 1.0 release, but validation is now supported and enforced on ServiceCollection::build_provider(). This change updates the return type, which is a breaking change.

Features

  • Validation

Validation

The consumers of a ServiceProvider expect that it is correctly configured and ready for use. There are edge cases, however, which could lead to invalid configuration, including:

  • A required, dependent service that has not been registered
  • A circular dependency, which will trigger a stack overflow
  • A singleton service with a scoped service dependency

Intrinsic validation has been added to ensure this cannot happen. The build_provider() function will return Result<ServiceProvider, ValidationError>, which will either contain a valid ServiceProvider or a ValidationError that will detail all of the errors. From that point forward, the ServiceProvider will be considered semantically correct and safe to use. The same validation process can also be invoked imperatively on-demand by using the di::validate function.

The ServiceDescriptorBuilder cannot automatically determine the dependencies your service may require. This means that validation is an explicit, opt-in capability. If you do not configure any dependencies for a ServiceDescriptor, then no validation will occur. ServiceDescriptorBuilder now has a depends_on method to add a ServiceDependency and ServiceDescriptor::dependencies accesses the configured dependencies:

pub enum ServiceCardinality {
    ZeroOrOne,
    ExactlyOne,
    ZeroOrMore,
}

pub struct ServiceDependency {
    injected_type: Type,
    cardinality: ServiceCardinality,
}

To make configuring services more succinct, 3 new utility functions have been added to the builder feature:

  • exactly_one - creates a ServiceDependency with a cardinality of 1:1
  • zero_or_one - creates a ServiceDependency with a cardinality of 0:1
  • zero_or_more - creates a ServiceDependency with a cardinality of 0:*

This enables the ability to define dependent service types and their cardinality to the target service.

fn main() {
    let mut services = ServiceCollection::new();

    services.add(
        singleton::<dyn Foo, FooImpl>()
        .from(|_| Rc::new(FooImpl::default())));
    services.add(
        transient::<dyn Bar, BarImpl>()
        .depends_on(exactly_one::<dyn Foo>())
        .from(|sp| Rc::new(BarImpl::new(sp.get_required::<dyn Foo>()))));

    match services.build_provider() {
        Ok(provider) => {
            let bar = provider.get_required::<dyn Bar>();
            assert_eq!(&bar.speak(), "foo bar");
        },
        Err(error) => {
            println!("The service configuration is invalid.\n{}", &error.to_string());
        }
    }
}

When you use the inject feature and the #[injectable] attribute, validation is automatically generated.

Breaking Changes

  • ServiceCollection::build_provider() now has the signature pub fn build_provider(&self) -> Result<ServiceProvider, ValidationError>