[][src]Crate atelier_core

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 model builder API that allow for a more fluent and less repetative construction of a core model.
  3. The prelude module contains the set of shapes defined in the Smithy specification.
  4. Traits for model actions used to implement linters, validators, and transformations.
  5. Traits for reading/writing model files in different representations.
  6. 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.

use atelier_core::model::shapes::{
    AppliedTrait, MemberShape, Operation, Resource, Service, Shape, 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),
);
let mut pattern_trait = AppliedTrait::new(prelude.make_shape("pattern".parse().unwrap()));
pattern_trait.set_value(Value::String(r"^\d\d\d\d\-\d\d-\d\d$".to_string()));
date.apply_trait(pattern_trait);

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

// ----------------------------------------------------------------------------------------
let shape_name = namespace.make_shape("GetMessageOutput".parse().unwrap());
let mut output = StructureOrUnion::new();
let mut message = MemberShape::new(
    shape_name.make_member("message".parse().unwrap()),
    prelude.make_shape("String".parse().unwrap()),
);
let required = AppliedTrait::new(prelude.make_shape("required".parse().unwrap()));
message.apply_trait(required);
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(
    shape_name.make_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),
);
let required = AppliedTrait::new(prelude.make_shape("readonly".parse().unwrap()));
get_message.apply_trait(required);

// ----------------------------------------------------------------------------------------
let mut message = Resource::default();
message.add_identifier("date".to_string(), Value::String(date.id().to_string()));
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),
);
let documentation = AppliedTrait::with_value(
    prelude.make_shape("documentation".parse().unwrap()),
    Value::String("Provides a Message of the day.".to_string()),
);
service.apply_trait(documentation);

// ----------------------------------------------------------------------------------------
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.
use atelier_core::error::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;

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(),
    )
    .into();

Modules

action

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.

builder

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

error

Standard Error, ErrorKind, and Result types.

io

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.

model

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

prelude

Provides an implementation of the prelude model described in the Smithy specification.

syntax

String constants for elements of the model.

Enums

Version

Versions of the Smithy specification.