Crate atelier_core[][src]

Expand description

This crate provides a Rust native implementaton of the the AWS Smithy semantic model and foundational capabilities..

The semantic model (a component of the Smithy framework) is the core representation used by tools in the Smithy build process. This crate provides an implementation of the semantic model the for the Atelier set of crates, and core traits for other crates. Specifically it provides:

  1. The semantic model itself that represents a Smithy model. This API is the in-memory representation shared by all Atelier crates and tools.
  2. A set of model builders that allow for a more fluent and less repetative construction of a core model.
  3. A pair of traits for model io and helper functions for reading/writing models.
  4. The prelude module contains the set of shapes defined in the Smithy specification.
  5. Traits for model actions used to implement linters, validators, and transformations.
  6. Traits for reading/writing model files in different representations.
    1. A model writer that emits a stable line-oriented format) useful for capturing test results.
    2. A model writer using Rust’s debug trait.
  7. A common error module to be used by all Atelier crates.

The Semantic Model API Example

The following example demonstrates the core model API to create a model for a simple service. The service, MessageOfTheDay has a single resource Message. The resource has an identifier for the date, but the read operation does not make the date member required and so will return the message for the current date.

This API acts as a set of generic data objects and as such has a tendency to be verbose in the construction of models. The need to create a lot of Identifier and ShapeID instances, for example, does impact the readability. It is important to note, that while there is a discussion in the Smithy specification contains the notion of both absolute and relative shape identifiers it is important to note that relative identifiers are not supported in the semantic model. All names in the semantic model must be resolved to an absolute name.

For more information, see the Rust Atelier book.

use atelier_core::model::identity::{HasIdentity, Identifier};
use atelier_core::model::shapes::{
    HasTraits, MemberShape, Operation, Resource, Service,
    ShapeKind, Simple, StructureOrUnion, TopLevelShape,
};
use atelier_core::model::values::Value;
use atelier_core::model::{Model, NamespaceID};
use atelier_core::prelude::PRELUDE_NAMESPACE;
use atelier_core::Version;

let prelude: NamespaceID = PRELUDE_NAMESPACE.parse().unwrap();
let namespace: NamespaceID = "example.motd".parse().unwrap();

// ----------------------------------------------------------------------------------------
let mut date = TopLevelShape::new(
    namespace.make_shape("Date".parse().unwrap()),
    ShapeKind::Simple(Simple::String),
);
date
    .apply_with_value(
        prelude.make_shape("pattern".parse().unwrap()),
        Value::String(r"^\d\d\d\d\-\d\d-\d\d$".to_string()).into()
    )
    .unwrap();

// ----------------------------------------------------------------------------------------
let shape_name = namespace.make_shape("BadDateValue".parse().unwrap());
let mut body = StructureOrUnion::new();
body.add_member(
    "errorMessage".parse().unwrap(),
    prelude.make_shape("String".parse().unwrap()),
);
let mut error = TopLevelShape::new(shape_name, ShapeKind::Structure(body));
error
    .apply_with_value(
        prelude.make_shape("error".parse().unwrap()),
        Some("client".to_string().into()),
    )
    .unwrap();

// ----------------------------------------------------------------------------------------
let shape_name = namespace.make_shape("GetMessageOutput".parse().unwrap());
let mut output = StructureOrUnion::new();
let mut message = MemberShape::new(
    "message".parse().unwrap(),
    prelude.make_shape("String".parse().unwrap()),
);
message
    .apply(prelude.make_shape("required".parse().unwrap()))
    .unwrap();
let _ = output.add_a_member(message);
let output = TopLevelShape::new(
    namespace.make_shape("GetMessageOutput".parse().unwrap()),
    ShapeKind::Structure(output),
);

// ----------------------------------------------------------------------------------------
let shape_name = namespace.make_shape("GetMessageInput".parse().unwrap());
let mut input = StructureOrUnion::new();
input.add_member(
    "date".parse().unwrap(),
    date.id().clone(),
);
let input = TopLevelShape::new(
    namespace.make_shape("GetMessageInput".parse().unwrap()),
    ShapeKind::Structure(input),
);

// ----------------------------------------------------------------------------------------
let mut get_message = Operation::default();
get_message.set_input_shape(&input);
get_message.set_output_shape(&output);
get_message.add_error_shape(&error);
let mut get_message = TopLevelShape::new(
    namespace.make_shape("GetMessage".parse().unwrap()),
    ShapeKind::Operation(get_message),
);
get_message
    .apply(prelude.make_shape("readonly".parse().unwrap()))
    .unwrap();

// ----------------------------------------------------------------------------------------
let mut message = Resource::default();
message.add_identifier(Identifier::new_unchecked("date"), date.id().clone());
message.set_read_operation_shape(&get_message);
let message = TopLevelShape::new(
    namespace.make_shape("Message".parse().unwrap()),
    ShapeKind::Resource(message),
);

// ----------------------------------------------------------------------------------------
let mut service = Service::new("2020-06-21");
service.add_resource_shape(&message);
let mut service = TopLevelShape::new(
    namespace.make_shape("MessageOfTheDay".parse().unwrap()),
    ShapeKind::Service(service),
);
service
    .apply_with_value(
        prelude.make_shape("documentation".parse().unwrap()),
        Value::String("Provides a Message of the day.".to_string()).into(),
    )
    .unwrap();

// ----------------------------------------------------------------------------------------
let mut model = Model::new(Version::V10);
model.add_shape(message);
model.add_shape(date);
model.add_shape(get_message);
model.add_shape(input);
model.add_shape(output);
model.add_shape(error);

println!("{:#?}", model);

The Model Builder API Example

The following example demonstrates the builder interface to create the same service as the example above. Hopefully this is more readable as it tends to be less repetative, uses &str for identifiers, and includes helper functions for common traits for example. It provides this better construction experience (there are no read methods on builder objects) by compromising two aspects:

  1. The API itself is very repetative; this means the same method may be on multiple objects, but makes it easier to use. For example, you want to add the documentation trait to a shape, so you can:
    1. construct a Trait entity using the core model and the Builder::add_trait method,
    2. use the TraitBuilder::documentation method which also takes the string to use as the trait value and returns a new TraitBuilder, or
    3. use the Builder::documentation method that hides all the details of a trait and just takes a string.
  2. It hides a lot of the Identifier and ShapeID construction and so any of those calls to from_str may fail when the code unwraps the result. This means the builder can panic in ways the core model does not.

For more information, see the Rust Atelier book.

use atelier_core::builder::traits::ErrorSource;
use atelier_core::builder::values::{ArrayBuilder, ObjectBuilder};
use atelier_core::builder::{
    traits, ListBuilder, MemberBuilder, ModelBuilder, OperationBuilder, ResourceBuilder,
    ServiceBuilder, ShapeTraits, SimpleShapeBuilder, StructureBuilder, TraitBuilder,
};
use atelier_core::model::{Identifier, Model, ShapeID};
use atelier_core::Version;
use std::convert::TryInto;

let model: Model = ModelBuilder::new(Version::V10, "example.motd")
    .service(
        ServiceBuilder::new("MessageOfTheDay", "2020-06-21")
            .documentation("Provides a Message of the day.")
            .resource("Message")
            .into(),
    )
    .resource(
        ResourceBuilder::new("Message")
            .identifier("date", "Date")
            .read("GetMessage")
            .into(),
    )
    .simple_shape(
        SimpleShapeBuilder::string("Date")
            .apply_trait(traits::pattern(r"^\d\d\d\d\-\d\d-\d\d$"))
            .into(),
    )
    .operation(
        OperationBuilder::new("GetMessage")
            .readonly()
            .input("GetMessageInput")
            .output("GetMessageOutput")
            .error("BadDateValue")
            .into(),
    )
    .structure(
        StructureBuilder::new("GetMessageInput")
            .member("date", "Date")
            .into(),
    )
    .structure(
        StructureBuilder::new("GetMessageOutput")
            .add_member(MemberBuilder::string("message").required().into())
            .into(),
    )
    .structure(
        StructureBuilder::new("BadDateValue")
            .error_source(ErrorSource::Client)
            .add_member(MemberBuilder::string("errorMessage").required().into())
            .into(),
    )
    .try_into().unwrap();

Modules

This module provides a set of traits that describes actions that can operate on models. These actions take three major forms; linters, validators, and transformers.

Builders to construct models in a more fluent style. See the example in the library overview.

Standard Error, ErrorKind, and Result types.

Traits for reading and writing models in different formats. Separate crates implement the ability to handle different representations, such as the original Smithy, JSON AST, and OpenAPI.

The Smithy semantic model itself, consisting of shapes, members, values, and model statements.

Provides constant valued names from the prelude model described in the Smithy specification.

String constants for elements of the model.

Macros

This macro will create a new Selector instance using the ShapeType variant provided.

Enums

Versions of the Smithy specification.