#order #standard #menu #restaurant #food #validation #field

open_menu_standard

Rust implementation of the OpenMenuStandard (OMS) specification

1 unstable release

new 0.1.0 May 14, 2025

#302 in Data structures

MIT license

150KB
3K SLoC

OpenMenuStandard Rust Implementation - README

This repository contains a Rust implementation of the OpenMenuStandard (OMS) specification. The library provides a complete data model, validation, serialization/deserialization, and utility functions for working with OpenMenuStandard documents.

Features

  • Complete implementation of the OpenMenuStandard 1.0 specification
  • Comprehensive data model with all required and optional fields
  • JSON serialization and deserialization using serde
  • Document validation using validator
  • Utility functions for creating, parsing, and manipulating OMS documents
  • Support for OMS URL scheme parsing and generation
  • Extensive test coverage

Getting Started

Prerequisites

Add the following dependencies to your Cargo.toml:

[dependencies]
open-menu-standard = "0.1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
validator = { version = "0.16", features = ["derive"] }
thiserror = "1.0"

Basic Usage

use open_menu_standard::{create_minimal_document, parse_oms_document};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a minimal document
    let document = create_minimal_document(
        "subway-usa",
        "Subway",
        "restaurant",
        "italian-bmt",
        "Italian B.M.T.",
        "sandwich"
    )?;
    
    // Serialize to JSON
    let json = document.to_json()?;
    println!("{}", json);
    
    // Parse from JSON
    let parsed_document = parse_oms_document(&json)?;
    
    // Generate a URL for the document
    let url = parsed_document.create_url().unwrap();
    println!("OMS URL: {}", url);
    
    Ok(())
}

Examples

Creating a Sandwich Menu

This example creates a menu for a sandwich shop with customization options and nutritional information.

use open_menu_standard::{
    OmsDocument, Metadata, Vendor, Item, Nutrition, Customization,
    CustomizationType, CustomizationDefault, CustomizationOption,
    MeasurementValue, NutrientWithDetails,
};
use chrono::Utc;
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create metadata
    let metadata = Metadata {
        created: Utc::now(),
        source: "sandwich-builder-app".to_string(),
        locale: "en-US".to_string(),
    };
    
    // Create vendor
    let vendor = Vendor {
        id: "subway-usa".to_string(),
        name: "Subway".to_string(),
        r#type: "restaurant".to_string(),
        location_id: Some("store-1234".to_string()),
        location_name: Some("Downtown".to_string()),
        address: None,
        contact: None,
        hours: None,
        cuisine: None,
        services: None,
    };
    
    // Create nutrition information
    let mut nutrition = Nutrition {
        serving_size: Some(MeasurementValue {
            value: 240.0,
            unit: "g".to_string(),
        }),
        calories: Some(410.0),
        protein: Some(MeasurementValue {
            value: 22.0,
            unit: "g".to_string(),
        }),
        fat: Some(NutrientWithDetails {
            value: 16.0,
            unit: "g".to_string(),
            details: None,
        }),
        carbohydrates: Some(NutrientWithDetails {
            value: 47.0,
            unit: "g".to_string(),
            details: None,
        }),
        sodium: None,
        cholesterol: None,
        vitamins: None,
        minerals: None,
        allergens: Some(vec!["wheat".to_string(), "dairy".to_string()]),
        dietary_flags: Some(vec!["contains_gluten".to_string()]),
        health_claims: None,
        ingredients: None,
        nutrition_standards: None,
    };
    
    // Create bread customization
    let bread_customization = Customization {
        id: "bread".to_string(),
        name: "Bread Type".to_string(),
        r#type: CustomizationType::SingleSelect,
        required: true,
        default: CustomizationDefault::String("italian-herbs".to_string()),
        min_selections: None,
        max_selections: None,
        min: None,
        max: None,
        step: None,
        unit_price_adjustment: None,
        unit_nutrition_adjustments: None,
        options: Some(vec![
            CustomizationOption {
                id: "italian-herbs".to_string(),
                name: "Italian Herbs & Cheese".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: Some(vec!["wheat".to_string(), "dairy".to_string()]),
                dietary_flags: Some(vec!["contains_gluten".to_string()]),
            },
            CustomizationOption {
                id: "wheat".to_string(),
                name: "Wheat Bread".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: Some(vec!["wheat".to_string()]),
                dietary_flags: Some(vec!["contains_gluten".to_string()]),
            },
            CustomizationOption {
                id: "flatbread".to_string(),
                name: "Flatbread".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: Some(vec!["wheat".to_string()]),
                dietary_flags: Some(vec!["contains_gluten".to_string()]),
            },
        ]),
    };
    
    // Create cheese customization
    let cheese_customization = Customization {
        id: "cheese".to_string(),
        name: "Cheese".to_string(),
        r#type: CustomizationType::SingleSelect,
        required: true,
        default: CustomizationDefault::String("american".to_string()),
        min_selections: None,
        max_selections: None,
        min: None,
        max: None,
        step: None,
        unit_price_adjustment: None,
        unit_nutrition_adjustments: None,
        options: Some(vec![
            CustomizationOption {
                id: "american".to_string(),
                name: "American Cheese".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: Some(vec!["dairy".to_string()]),
                dietary_flags: None,
            },
            CustomizationOption {
                id: "provolone".to_string(),
                name: "Provolone Cheese".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: Some(vec!["dairy".to_string()]),
                dietary_flags: None,
            },
            CustomizationOption {
                id: "none".to_string(),
                name: "No Cheese".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: None,
                dietary_flags: None,
            },
        ]),
    };
    
    // Create vegetables customization
    let vegetables_customization = Customization {
        id: "vegetables".to_string(),
        name: "Vegetables".to_string(),
        r#type: CustomizationType::MultiSelect,
        required: false,
        default: CustomizationDefault::StringArray(vec![
            "lettuce".to_string(), 
            "tomato".to_string(), 
            "onion".to_string()
        ]),
        min_selections: Some(0),
        max_selections: Some(10),
        min: None,
        max: None,
        step: None,
        unit_price_adjustment: None,
        unit_nutrition_adjustments: None,
        options: Some(vec![
            CustomizationOption {
                id: "lettuce".to_string(),
                name: "Lettuce".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: None,
                dietary_flags: None,
            },
            CustomizationOption {
                id: "tomato".to_string(),
                name: "Tomato".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: None,
                dietary_flags: None,
            },
            CustomizationOption {
                id: "onion".to_string(),
                name: "Onion".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: None,
                dietary_flags: None,
            },
            CustomizationOption {
                id: "cucumber".to_string(),
                name: "Cucumber".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: None,
                dietary_flags: None,
            },
            CustomizationOption {
                id: "peppers".to_string(),
                name: "Bell Peppers".to_string(),
                price_adjustment: Some(0.0),
                nutrition_adjustments: None,
                allergens: None,
                dietary_flags: None,
            },
        ]),
    };
    
    // Create sandwich item
    let item = Item {
        id: "italian-bmt".to_string(),
        name: "Italian B.M.T.".to_string(),
        category: "sandwich".to_string(),
        vendor_id: None,
        description: Some("Italian B.M.T.® sandwich with Genoa salami, spicy pepperoni, and Black Forest ham".to_string()),
        subcategory: None,
        image_url: Some("https://example.com/images/italian-bmt.jpg".to_string()),
        base_price: Some(7.99),
        currency: Some("USD".to_string()),
        nutrition: Some(nutrition),
        customizations: Some(vec![
            bread_customization,
            cheese_customization,
            vegetables_customization,
        ]),
        selected_customizations: None,
        quantity: None,
        item_note: None,
        calculated: None,
        components: None,
        availability: None,
        popularity: None,
    };
    
    // Create the OMS document
    let document = OmsDocument::new(metadata, vendor, vec![item]);
    
    // Validate and serialize
    document.validate()?;
    let json = document.to_json()?;
    println!("{}", json);
    
    Ok(())
}

Parsing an OMS URL

use open_menu_standard::parse_oms_url;

fn main() {
    let url = "omenu://order?v=subway-usa&l=store-1234&i=italian-bmt";
    
    if let Some(params) = parse_oms_url(url) {
        println!("Action: {}", params.get("action").unwrap_or(&"".to_string()));
        println!("Vendor: {}", params.get("v").unwrap_or(&"".to_string()));
        println!("Location: {}", params.get("l").unwrap_or(&"".to_string()));
        println!("Item: {}", params.get("i").unwrap_or(&"".to_string()));
    } else {
        println!("Invalid OMS URL");
    }
}

Setting Up NFC Tags

This library can be used to generate content for NFC tags that implement the tap-to-order functionality in the OpenMenuStandard specification:

use open_menu_standard::{
    OmsDocument, parse_oms_document, create_minimal_document
};

// Create or load an OMS document
let document = create_minimal_document(
    "burger-joint",
    "Burger Joint",
    "restaurant",
    "cheeseburger",
    "Classic Cheeseburger",
    "burger"
)?;

// Generate an OMS URL for NFC tag
let url = document.create_url().unwrap();
println!("URL for NFC tag: {}", url);

// Alternatively, for more complex data, generate a compact JSON representation
let json = document.to_json()?;
println!("JSON for NFC tag: {}", json);

API Documentation

Core Types

  • OmsDocument: The main container for an OpenMenuStandard document
  • Metadata: Information about the document itself
  • Vendor: Information about the food service provider
  • Item: Representation of a food or beverage product
  • Nutrition: Nutritional information about an item
  • Customization: Ways in which an item can be modified
  • Order: Collection of items being ordered

Main Functions

  • OmsDocument::new(): Create a new OMS document
  • OmsDocument::validate(): Validate a document against the specification
  • OmsDocument::to_json(): Serialize a document to JSON
  • OmsDocument::from_json(): Deserialize a document from JSON
  • OmsDocument::create_url(): Generate an OMS URL for the document
  • parse_oms_document(): Parse a document from JSON
  • create_minimal_document(): Create a basic document with minimal fields
  • parse_oms_url(): Parse an OMS URL and extract parameters

Resources

License

This library is licensed under the MIT License - see the LICENSE file for details.

Dependencies

~8–21MB
~286K SLoC