This is a work in progress!
Parse the metadata XML describing an SAP OData service and generate basic Rust entities for each EDM type:
ComplexType
EntityType
FunctionImport
Currently when generating a Rust struct
, only the Name
and Type
properties are extracted from the XML <EntityType>
declaration.
Consider how the other XML attribute values and SAP annotations could be made available within the generated Rust struct
.
Certain XML properties within some of the entity sets in the demo OData service GWSAMPLE_BASIC
contain values that are not valid XML.
Consequently, when quick_xml
encounters such values, it throws its toys out the pram.
Therefore, the raw XML string must first be sanitised before attempting to it.
See the README of parse-sap-odata-demo
for more details.
Edm.DateTime
and Edm.Decimal
as fields of these types are currently interpreted simply as String
and f64
fields respectively.You are writing a Rust application to consume data from an SAP OData V2 service.
The functionality in this crate is invoked by the build script in your application and generates the struct
s and enum
s needed to consume the data returned by callin the OData service's entity sets.
Your app can then consume the entity set data using the code in crate parse-sap-atom-feed
In the Cargo.toml
of your application, define an entry in [build-dependencies]
that points to the parse-sap-odata
crate:
toml
[build-dependencies]
parse-sap-odata = "1.1"
Your app will require at least these dependencies:
toml
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
parse-sap-atom-feed = "0.1"
rust_decimal = "1.30"
serde = { version = "1.0", features = ["derive"] }
uuid = { version = "1.4", features = ["serde"] }
All metadata XML for the OData services your app consumes must be located in the ./odata
directory immediately under your app's top level directory.
Using the demo service GWSAMPLE_BASIC
available from SAP's Dev Center server, display the metadata XML for this service, then save that in file ./odata/gwsample_basic.xml
.
In your app's build script (build.rs
), run the generator for your desired OData service:
```rust use parsesapodata::parser::gen_src;
fn main() {
gensrc(
"gwsamplebasic", // metadatafilename. The ".xml" extension is assumed
"GWSAMPLE_BASIC" // Value of the Namespace attribute on the
More information about Rust build scripts is available on the documentation site.
If cargo
detects a build.rs
file in your project/crate, then it automatically populates the environment variable OUT_DIR
and runs build.rs
before compiling your application.
The OUT_DIR
variable then points to the directory into which all build script output is written.
The default directory name is target/debug/build/<your_package_name>/out
, and this is where you can find the generated struct
declarations for the OData service.
You can specify your own value for OUT_DIR
either by passing a value to cargo
's --out_dir
flag, or by defining your own location in a config.toml
file in the ./.cargo
directory.
See Cargo Configuration for more details.
All generated struct
s implement at least the following traits #[derive(Clone, Debug, Default)]
In the source code of your application, you can reference the generated source code like this:
```rust // Include the generated code include!(concat!(env!("OUTDIR"), "/gwsamplebasic.rs"));
// Use the BusinessPartner struct for example fn main() { let bp: BusinessPartner = Default::default(); println!("{:#?}", bp); } ```
In the event an Entity Type definition uses a complex type, then the complex type is first created as a Rust struct
.
The field in Rust struct
that has this complex type is then defined using this struct
.
An example of this is the Address
property.
```xml
```
The Rust struct
name is generated by trimming the namespace qualifier and (if present) the CT_
prefix
xml
<ComplexType Name="CT_Address">
<Property Name="City" Type="Edm.String" MaxLength="40" sap:label="City" sap:semantics="city"/>
<Property Name="PostalCode" Type="Edm.String" MaxLength="10" sap:label="Postal Code" sap:semantics="zip"/>
<Property Name="Street" Type="Edm.String" MaxLength="60" sap:label="Street" sap:semantics="street"/>
<Property Name="Building" Type="Edm.String" MaxLength="10" sap:label="Building"/>
<Property Name="Country" Type="Edm.String" MaxLength="3" sap:label="Country" sap:semantics="country"/>
<Property Name="AddressType" Type="Edm.String" MaxLength="2" sap:label="Address Type"/>
</ComplexType>
So the above XML definition becomes:
```rust
pub struct Address {
pub addresstype: Option
The metadata for the GWSAMPLE_BASIC
OData service contains the following complex type:
xml
<ComplexType Name="CT_String">
<Property Name="String" Type="Edm.String" Nullable="false" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
</ComplexType>
Allowing for the current situation in which additional attribute values and SAP Annotations are not preserved, this particular type turns out not to be complex at all — its just a String
.
In such cases, fields declared to be of these "simple" complex types (such as CT_String
), are collapsed down to the Rust native type of the single inner property — which in this example is simply a String
.
On the basis that a single OData service exposes a static list of entity sets, and within the scope of any single request, you will only ever be interacting with a single entity set, it makes sense to treat each entity set name as an enum
variant.
Under the <Schema>
element in the OData service document, there is an <EntityContainer>
element.
All entity sets available through this OData service are identified here with their own <EntitySet Name="<some_name>">
tag.
The following naming convention is used: <odata_service_name>Entities
.
For example, the entity sets belonging to the OData service GWSAMPLE_BASIC
become the following enum
:
```rust
pub enum GwsampleBasicEntities { BusinessPartnerSet, ProductSet, SalesOrderSet, SalesOrderLineItemSet, ContactSet, VhSexSet, VhCountrySet, VhAddressTypeSet, VhCategorySet, VhCurrencySet, VhUnitQuantitySet, VhUnitWeightSet, VhUnitLengthSet, VhProductTypeCodeSet, VhBpRoleSet, VhLanguageSet, } ```
Three convenience functions are then implemented for enum GwsampleBasicEntities
:
rust
impl GwsampleBasicEntities {
pub const fn value(&self) -> &'static str { /* SNIP */ }
pub fn iterator() -> impl Iterator<Item = GwsampleBasicEntities> { /* SNIP */ }
pub fn as_list() -> Vec<&'static str> { /* SNIP */ }
}
value
functionThis function returns the name of the entity set variant as a static string slice:
rust
GwsampleBasicEntities::ProductSet::value(); // -> "ProductSet"
iterator
functionFor standard Rust enums
such as Option
and Result
, it makes little sense to attempt to loop over their variants simply because these enum
s exist specifically to gather together diverse types into a single object.
E.G. The Option
enum
exists to provide a type-safe mechanism for handling the possibility that a variable might not contain a value.
However, an OData service guarantees that the entity set names form an immutable, type-safe list.
Therefore, on the basis of this guarantee, it is now both safe and meaningful to implement an iterator
function.
as_list
functionBy making use of the above iterator
and value
functions, the as_list
function returns the names of the entity sets as a vector of string slices.